/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/


/* -------------------------------------------------------------------------- */

#include "nu_lang_defs.h"
#include "nu_variant.h"
#include "nu_exception.h"
#include "nu_os_std.h"
#include "nu_error_codes.h"

#include <functional>
#include <cmath>
#include <iomanip>
#include <cassert>
#include <string>

/* -------------------------------------------------------------------------- */

namespace nu
{

/* -------------------------------------------------------------------------- */

variant_t::variant_t(const string_t& value, type_t t)
{
   switch (t)
   {
      case type_t::BYTE:
         build<byte_t, type_t::BYTE>(std::stoi(value) & 0xff);
         break;

      case type_t::FLOAT:
         build<float_t, type_t::FLOAT>(std::stof(value));
         break;

      case type_t::DOUBLE:
         build<double_t, type_t::DOUBLE>(std::stod(value));
         break;

      case type_t::INTEGER:
         build<int_t, type_t::INTEGER>(std::stoi(value));
         break;

      case type_t::BOOLEAN:
         build<bool_t, type_t::BOOLEAN>(value == PROGLANG_BOOL_TRUE);
         break;

      case type_t::STRING:
         build<string_t, type_t::STRING>(value);
         break;

      case type_t::UNDEFINED:
         break;
   }

}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const string_t& value) throw()
{
   build<string_t, type_t::STRING>(value);
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const byte_t& value) throw()
{
   build<byte_t, type_t::BYTE>(value);
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const float_t& value) throw()
{
   build<float_t, type_t::FLOAT>(value);
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const double_t& value) throw()
{
   build<double_t, type_t::DOUBLE>(value);
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const int_t& value) throw()
{
   build<int_t, type_t::INTEGER>(value);
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const bool_t& value) throw()
{
   build<bool_t, type_t::BOOLEAN>(value);
}


/* -------------------------------------------------------------------------- */

const char* variant_t::get_type_desc(const type_t& type) throw()
{
   switch (type)
   {

      case type_t::BYTE:
         return PROGLANG_TYPE_BYTE;

      case type_t::FLOAT:
         return PROGLANG_TYPE_FLOAT;

      case type_t::DOUBLE:
         return PROGLANG_TYPE_DOUBLE;

      case type_t::INTEGER:
         return PROGLANG_TYPE_INTEGER;

      case type_t::STRING:
         return PROGLANG_TYPE_STRING;

      case type_t::BOOLEAN:
         return PROGLANG_TYPE_BOOLEAN;

      case type_t::UNDEFINED:
         return PROGLANG_TYPE_UNDEFINED;

      default:
         assert(0);
   }

   return PROGLANG_TYPE_UNDEFINED;
}


/* -------------------------------------------------------------------------- */

bool variant_t::operator<(const variant_t& b) const
{
   auto a_ptr = this;
   auto b_ptr = &b;

   while (a_ptr->_ref_value) a_ptr = a_ptr->_ref_value;

   while (b_ptr->_ref_value) b_ptr = b_ptr->_ref_value;

   rt_error_code_t::get_instance().throw_if(
      a_ptr->_data_ptr == nullptr ||
      b_ptr->_data_ptr == nullptr,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   return *(a_ptr->_data_ptr) < *(b_ptr->_data_ptr);
}


/* -------------------------------------------------------------------------- */

bool variant_t::operator==(const variant_t& b) const
{
   auto a_ptr = this;
   auto b_ptr = &b;

   while (a_ptr->_ref_value) a_ptr = a_ptr->_ref_value;

   while (b_ptr->_ref_value) b_ptr = b_ptr->_ref_value;

   rt_error_code_t::get_instance().throw_if(
      a_ptr->_data_ptr == nullptr ||
      b_ptr->_data_ptr == nullptr,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   return *(a_ptr->_data_ptr) == *(b_ptr->_data_ptr);
}


/* -------------------------------------------------------------------------- */

variant_t operator +(const variant_t& a, const variant_t& b)
{
   auto a_ptr = &a;
   auto b_ptr = &b;

   while (a_ptr->_ref_value) a_ptr = a_ptr->_ref_value;

   while (b_ptr->_ref_value) b_ptr = b_ptr->_ref_value;

   rt_error_code_t::get_instance().throw_if(
      a_ptr->_data_ptr == nullptr ||
      b_ptr->_data_ptr == nullptr,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   return *(a_ptr->_data_ptr) + *(b_ptr->_data_ptr);
}


/* -------------------------------------------------------------------------- */

variant_t operator -(const variant_t& a, const variant_t& b)
{
   auto a_ptr = &a;
   auto b_ptr = &b;

   while (a_ptr->_ref_value) a_ptr = a_ptr->_ref_value;

   while (b_ptr->_ref_value) b_ptr = b_ptr->_ref_value;

   rt_error_code_t::get_instance().throw_if(
      a_ptr->_data_ptr == nullptr ||
      b_ptr->_data_ptr == nullptr,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   return *(a_ptr->_data_ptr) - *(b_ptr->_data_ptr);
}


/* -------------------------------------------------------------------------- */

variant_t operator /(const variant_t& a, const variant_t& b)
{
   auto a_ptr = &a;
   auto b_ptr = &b;

   while (a_ptr->_ref_value) a_ptr = a_ptr->_ref_value;

   while (b_ptr->_ref_value) b_ptr = b_ptr->_ref_value;

   rt_error_code_t::get_instance().throw_if(
      a_ptr->_data_ptr == nullptr ||
      b_ptr->_data_ptr == nullptr,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   return *(a_ptr->_data_ptr) / *(b_ptr->_data_ptr);
}


/* -------------------------------------------------------------------------- */

variant_t operator *(const variant_t& a, const variant_t& b)
{
   auto a_ptr = &a;
   auto b_ptr = &b;

   while (a_ptr->_ref_value) a_ptr = a_ptr->_ref_value;

   while (b_ptr->_ref_value) b_ptr = b_ptr->_ref_value;

   rt_error_code_t::get_instance().throw_if(
      a_ptr->_data_ptr == nullptr ||
      b_ptr->_data_ptr == nullptr,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   return (*(a_ptr->_data_ptr)) * (*(b_ptr->_data_ptr));
}


/* -------------------------------------------------------------------------- */

variant_t& variant_t::operator+=(const variant_t& b)
{
   if (_ref_value)
      return _ref_value->operator+=(b);

   rt_error_code_t::get_instance().throw_if(
      is_const(),
      0,
      rt_error_code_t::E_CANNOT_MOD_CONST,
      "");

   *this = *this + b;

   return *this;
}


/* -------------------------------------------------------------------------- */

variant_t& variant_t::operator-=(const variant_t& b)
{
   if (_ref_value)
      return _ref_value->operator-=(b);

   rt_error_code_t::get_instance().throw_if(
      is_const(),
      0,
      rt_error_code_t::E_CANNOT_MOD_CONST,
      "");

   *this = *this - b;

   return *this;
}


/* -------------------------------------------------------------------------- */

variant_t variant_t::power(const variant_t& b) const
{
   if (_ref_value)
      return _ref_value->power(b);

   rt_error_code_t::get_instance().throw_if(
      is_const(),
      0,
      rt_error_code_t::E_CANNOT_MOD_CONST,
      "");


   if (get_type() == variant_t::type_t::DOUBLE ||
         b.get_type() == variant_t::type_t::DOUBLE)
   {
      return variant_t(double_t(pow(to_double(), b.to_double())));
   }

   if (get_type() == variant_t::type_t::FLOAT ||
         b.get_type() == variant_t::type_t::FLOAT)
   {
      return variant_t(float_t(pow(to_float(), b.to_float())));
   }

   if (get_type() == variant_t::type_t::INTEGER &&
         b.get_type() == variant_t::type_t::INTEGER)
   {
      return variant_t(int(0.5F + ::pow(to_double(), b.to_double())));
   }

   rt_error_code_t::get_instance().throw_if(
      true,
      0,
      rt_error_code_t::E_TYPE_MISMATCH,
      "");

   return variant_t();
}


/* -------------------------------------------------------------------------- */

variant_t variant_t::int_mod(const variant_t& b) const
{
   if (_ref_value)
      return _ref_value->int_mod(b);

   if (get_type() == variant_t::type_t::INTEGER &&
         b.get_type() == variant_t::type_t::INTEGER)
   {
      rt_error_code_t::get_instance().throw_if(
         b.to_int() == 0,
         0,
         rt_error_code_t::E_DIV_BY_ZERO,
         "");

      return variant_t(to_int() % b.to_int());
   }

   rt_error_code_t::get_instance().throw_if(
      true,
      0,
      rt_error_code_t::E_TYPE_MISMATCH,
      "");

   return variant_t();
}


/* -------------------------------------------------------------------------- */

void variant_t::increment()
{
   if (_ref_value)
   {
      _ref_value->increment();
   }

   else
   {
      rt_error_code_t::get_instance().throw_if(
         is_const(),
         0,
         rt_error_code_t::E_CANNOT_MOD_CONST,
         "");

      rt_error_code_t::get_instance().throw_if(
         ! _data_ptr || ! _data_ptr->is_numeric(),
         0,
         rt_error_code_t::E_TYPE_ILLEGAL,
         "");

      _data_ptr->increment();
   }
}


/* -------------------------------------------------------------------------- */

void variant_t::decrement()
{
   if (_ref_value)
   {
      _ref_value->decrement();
   }

   else
   {
      rt_error_code_t::get_instance().throw_if(
         is_const(),
         0,
         rt_error_code_t::E_CANNOT_MOD_CONST,
         "");

      rt_error_code_t::get_instance().throw_if(
         !_data_ptr || !_data_ptr->is_numeric(),
         0,
         rt_error_code_t::E_TYPE_ILLEGAL,
         "");

      _data_ptr->decrement();
   }
}


/* -------------------------------------------------------------------------- */

std::ostream& operator << (std::ostream& os, const variant_t& val)
{
   if (val.is_reference())
      os << "&";

   switch (val.get_type())
   {
      case variant_t::type_t::FLOAT:
         os << PROGLANG_TYPE_FLOAT;
         break;

      case variant_t::type_t::DOUBLE:
         os << PROGLANG_TYPE_DOUBLE;
         break;

      case variant_t::type_t::INTEGER:
         os << PROGLANG_TYPE_INTEGER;
         break;

      case variant_t::type_t::BOOLEAN:
         os << PROGLANG_TYPE_BOOLEAN;
         break;

      case variant_t::type_t::STRING:
         os << PROGLANG_TYPE_STRING;
         break;

      case variant_t::type_t::BYTE:
         os << PROGLANG_TYPE_BYTE;
         break;

      case variant_t::type_t::UNDEFINED:
         os << PROGLANG_TYPE_UNDEFINED;
         break;
   }

   os << " ";

   return os;
}


/* -------------------------------------------------------------------------- */

bool variant_t::is_integer_literal(const std::string& value)
{
   if (value.empty())
      return false;

   auto is_intexpr = [](char c)
   {
      return(c >= '0' && c <= '9');
   };

   char first_char = value.c_str()[0];

   if (!is_intexpr(first_char) && first_char != '-')
      return false;

   if (value.size() == 1)
      return first_char != '-';

   for (size_t i = 1; i < value.size(); ++i)
   {
      const char c = value.c_str()[i];

      if (!is_intexpr(c))
         return false;
   }

   return true;
}


/* -------------------------------------------------------------------------- */

bool variant_t::is_float_literal(const std::string& value)
{
   if (value.empty())
      return false;

   auto is_intexpr = [](char c)
   {
      return(c >= '0' && c <= '9');
   };

   char first_char = value.c_str()[0];

   if (!is_intexpr(first_char) &&
         first_char != '-' &&
         first_char != '.')
   {
      return false;
   }

   if (value.size() == 1)
      return first_char != '-' && first_char != '.';

   char old_c = 0;
   int point_cnt = 0;
   int E_cnt = 0;

   for (size_t i = 0; i < value.size(); ++i)
   {
      const char c = value.c_str()[i];

      bool is_valid =
         (c == '-' && i == 0) ||
         is_intexpr(c) ||
         (c == '.' && point_cnt++ < 1) ||
         (::toupper(c) == 'E' && E_cnt++ < 1 && is_intexpr(old_c)) ||
         ((c == '+' || c == '-') && (::toupper(old_c) == 'E'));

      if (!is_valid)
         return false;

      old_c = c;
   }

   // we also accept expressions like '1E' (it is incomplete floating expression
   // parser will verify if next token match with +/- and an integer exponent)

   return !is_intexpr(old_c) && ::toupper(old_c) != 'E' ? false : true;
}


/* -------------------------------------------------------------------------- */

string_t variant_t::to_string() const
{
   if (_ref_value)
      return _ref_value->to_string();

   if (!_data_ptr)
      return string_t();

   return _data_ptr->to_string();
}


/* -------------------------------------------------------------------------- */

bool variant_t::is_numeric() const throw()
{
   const auto * _this = _ref_value ? _ref_value : this;

   switch (_this->get_type())
   {
      case type_t::INTEGER:
      case type_t::FLOAT:
      case type_t::DOUBLE:
      case type_t::BOOLEAN:
         return true;

      default:
         break;
   }

   return false;
}


/* -------------------------------------------------------------------------- */

bool variant_t::cast_to(type_t new_type)
{
   if (_ref_value)
      return _ref_value->cast_to(new_type);

   if (_data_ptr == nullptr)
      return false;

   if (get_type() == new_type)
      return true;

   auto ptr = std::move(_data_ptr);

   _data_ptr =
      std::make_unique<variant_data_t>(new_type);

   _data_ptr->cast_set(*ptr);

   return true;
}


/* -------------------------------------------------------------------------- */

bool variant_t::set(const variant_t& other)
{
   if (other.get_type() == type_t::UNDEFINED)
      return false;

   variant_t * const _this = _ref_value ? _ref_value : this;
   variant_t * const _other =
      (variant_t * const)(other._ref_value ? other._ref_value : &other);

   if (_this->_data_ptr == nullptr)
   {
      _this->_data_ptr = std::make_unique<variant_data_t>(*_other->_data_ptr);
   }

   else
   {
      _this->_data_ptr->cast_set(*_other->_data_ptr);
   }

   return true;
}


/* -------------------------------------------------------------------------- */

bool variant_t::to_bool() const
{
   if (_ref_value)
      return _ref_value->to_bool();

   if (!_data_ptr)
      return false;

   return _data_ptr->to_bool();
}


/* -------------------------------------------------------------------------- */

int_t variant_t::to_int() const
{
   if (_ref_value)
      return _ref_value->to_int();

   if (!_data_ptr)
      return 0;

   return _data_ptr->to_int();
}


/* -------------------------------------------------------------------------- */

double_t variant_t::to_double() const
{
   if (_ref_value)
      return _ref_value->to_double();

   if (!_data_ptr)
      return 0;

   return _data_ptr->to_double();
}


/* -------------------------------------------------------------------------- */

float_t variant_t::to_float() const
{
   if (_ref_value)
      return _ref_value->to_float();

   if (!_data_ptr)
      return 0;

   return _data_ptr->to_float();
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const variant_t& v)
   : _constant(v._constant),
     _ref_value(v._ref_value)
{
   if (v._data_ptr)
      _data_ptr = std::make_unique< variant_data_t >(*v._data_ptr);
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(const variant_data_t& vd)
   :
   _constant(false),
   _ref_value(nullptr),
   _data_ptr(std::make_unique< variant_data_t >(vd))
{
}


/* -------------------------------------------------------------------------- */

variant_t::variant_t(variant_t&& v)
   :
   _data_ptr(std::move(v._data_ptr)),
   _constant(std::move(v._constant)),
   _ref_value(std::move(v._ref_value))
{

}


/* -------------------------------------------------------------------------- */

variant_t& variant_t::operator=(variant_t&& v)
{
   if (this != &v)
   {
      rt_error_code_t::get_instance().throw_if(
         is_const(),
         0,
         rt_error_code_t::E_CANNOT_MOD_CONST,
         "");

      _data_ptr = std::move(v._data_ptr);
      _constant = std::move(v._constant);
      _ref_value = std::move(v._ref_value);
   }

   return *this;
}


/* -------------------------------------------------------------------------- */

variant_t& variant_t::operator=(const variant_t& v)
{
   if (this != &v)
   {
      rt_error_code_t::get_instance().throw_if(
         is_const(),
         0,
         rt_error_code_t::E_CANNOT_MOD_CONST,
         "");

      if (v._data_ptr)
         _data_ptr =
            std::move(std::make_unique< variant_data_t >(*v._data_ptr));

      else
         _data_ptr = nullptr;

      _constant = v._constant;
      _ref_value = v._ref_value;
   }

   return *this;
}



/* -------------------------------------------------------------------------- */
// variant_data_t
/* -------------------------------------------------------------------------- */

variant_data_t::variant_data_t(variable_t::type_t t) :
   _type(t)
{
   switch (t)
   {
      case type_t::UNDEFINED:
         break;

      case type_t::INTEGER:
         _ptr = build<int_t>();
         break;

      case type_t::FLOAT:
         _ptr = build<float_t>();
         break;

      case type_t::DOUBLE:
         _ptr = build<double_t>();
         break;

      case type_t::STRING:
         _ptr = build<string_t>();
         break;

      case type_t::BYTE:
         _ptr = build<byte_t>();
         break;

      case type_t::BOOLEAN:
         _ptr = build<bool_t>();
         break;

      default:
         assert(0);
         break;
   }
}


/* -------------------------------------------------------------------------- */

variant_data_t::variant_data_t(const variant_data_t & other) :
   _type(other._type)
{
   switch (_type)
   {
      case type_t::UNDEFINED:
         break;

      case type_t::INTEGER:
         _ptr = copy<int_t>(other);
         break;

      case type_t::FLOAT:
         _ptr = copy<float_t>(other);
         break;

      case type_t::DOUBLE:
         _ptr = copy<double_t>(other);
         break;

      case type_t::STRING:
         _ptr = copy<string_t>(other);
         break;

      case type_t::BYTE:
         _ptr = copy<byte_t>(other);
         break;

      case type_t::BOOLEAN:
         _ptr = copy<bool_t>(other);
         break;

      default:
         assert(0);
         break;
   }
}


/* -------------------------------------------------------------------------- */

variant_data_t::variant_data_t(variant_data_t && other) :
   _type(std::move(other._type))
{
   free_mem();
   _ptr = other._ptr;
   other._ptr = nullptr;
}


/* -------------------------------------------------------------------------- */

variant_data_t& variant_data_t::operator=(variant_data_t && other)
{
   if (this != &other)
   {
      _type = std::move(other._type);

      free_mem();
      _ptr = other._ptr;
      other._ptr = nullptr;
   }

   return *this;
}


/* -------------------------------------------------------------------------- */

void variant_data_t::set(const variant_data_t & other)
{
   if (this != &other)
   {
      bool same_type = _type == other._type;

      if (!same_type || _ptr == nullptr)
      {
         _type = other._type;
         free_mem();

         switch (_type)
         {
            case type_t::UNDEFINED:
               break;

            case type_t::INTEGER:
               _ptr = copy<int_t>(other);
               break;

            case type_t::FLOAT:
               _ptr = copy<float_t>(other);
               break;

            case type_t::DOUBLE:
               _ptr = copy<double_t>(other);
               break;

            case type_t::STRING:
               _ptr = copy<string_t>(other);
               break;

            case type_t::BYTE:
               _ptr = copy<byte_t>(other);
               break;

            case type_t::BOOLEAN:
               _ptr = copy<bool_t>(other);
               break;
         }
      }

      else
      {
         switch (_type)
         {
            case type_t::UNDEFINED:
               break;

            case type_t::INTEGER:
               int_value() = other.int_value();
               break;

            case type_t::FLOAT:
               float_value() = other.float_value();
               break;

            case type_t::DOUBLE:
               double_value() = other.double_value();
               break;

            case type_t::STRING:
               string_value() = other.string_value();
               break;

            case type_t::BYTE:
               byte_value() = other.byte_value();
               break;

            case type_t::BOOLEAN:
               bool_value() = other.bool_value();
               break;
         }
      }

   }
}


/* -------------------------------------------------------------------------- */

bool variant_data_t::invert_sign()
{
   bool res = true;

   switch (_type)
   {
      case type_t::INTEGER:
      {
         int_value() = -int_value();
         break;
      }

      case type_t::FLOAT:
      {
         float_value() = -float_value();
         break;
      }

      case type_t::DOUBLE:
      {
         double_value() = -double_value();
         break;
      }

      case type_t::STRING:
      case type_t::BYTE:
      case type_t::BOOLEAN:
      case type_t::UNDEFINED:
      default:
         res = false;
         break;
   }

   return res;
}


/* -------------------------------------------------------------------------- */

bool variant_data_t::invert()
{
   bool res = true;

   switch (_type)
   {
      case type_t::FLOAT:
      {
         if (float_value() == 0.0)
            return false;

         float_value() = 1.0F / float_value();
         break;
      }

      case type_t::DOUBLE:
      {
         if (double_value() == 0.0)
            return false;

         double_value() = 1.0F / double_value();
         break;
      }

      case type_t::STRING:
      case type_t::BYTE:
      case type_t::BOOLEAN:
      case type_t::UNDEFINED:
      case type_t::INTEGER:
      default:
         res = false;
         break;
   }

   return res;
}


/* -------------------------------------------------------------------------- */

bool variant_data_t::increment()
{
   bool res = true;

   switch (_type)
   {
      case type_t::FLOAT:
      {
         ++float_value();
         break;
      }

      case type_t::DOUBLE:
      {
         ++double_value();
         break;
      }

      case type_t::INTEGER:
      {
         ++int_value();
         break;
      }

      case type_t::BYTE:
      {
         ++byte_value();
         break;
      }

      case type_t::BOOLEAN:
      {
         ++bool_value();
         break;
      }

      case type_t::STRING:
      case type_t::UNDEFINED:
      default:
         res = false;
         break;
   }

   return res;
}


/* -------------------------------------------------------------------------- */

bool variant_data_t::decrement()
{
   bool res = true;

   switch (_type)
   {
      case type_t::FLOAT:
      {
         --float_value();
         break;
      }

      case type_t::DOUBLE:
      {
         --double_value();
         break;
      }

      case type_t::INTEGER:
      {
         --int_value();
         break;
      }

      case type_t::BYTE:
      {
         --byte_value();
         break;
      }

      case type_t::BOOLEAN:
      {
         ++bool_value();
         break;
      }

      case type_t::STRING:
      case type_t::UNDEFINED:
      default:
         res = false;
         break;
   }

   return res;
}


/* -------------------------------------------------------------------------- */

#define __VLT__(_T) \
do {\
      if( ! ((a.## _T ##_value()) < (b.to_ ## _T ())))\
         return false;\
   return true;\
} while (0)


/* -------------------------------------------------------------------------- */

bool variant_data_t::operator<(const variant_data_t & other) const
{
   using type_t = variant_data_t::type_t;

   rt_error_code_t::get_instance().throw_if(
      _type == type_t::UNDEFINED ||
      other._type == type_t::UNDEFINED,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");


   switch (_type)
   {
      case type_t::INTEGER:
         return int_value() < other.to_int();

      case type_t::FLOAT:
         return float_value() < other.to_float();

      case type_t::DOUBLE:
         return double_value() < other.to_double();

      case type_t::STRING:
         return string_value() < other.to_string();

      case type_t::BYTE:
         return byte_value() < other.to_byte();

      case type_t::BOOLEAN:
         return bool_value() < other.to_bool();

      default:
         break;
   }

   return false;
}


/* -------------------------------------------------------------------------- */

variant_data_t operator+(const variant_data_t & a, const variant_data_t & b)
{
   using type_t = variant_data_t::type_t;

   rt_error_code_t::get_instance().throw_if(
      a._type == type_t::UNDEFINED ||
      b._type == type_t::UNDEFINED,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   switch (a._type)
   {
      case type_t::INTEGER:
      {
         switch (b._type)
         {
            case type_t::DOUBLE:
            case type_t::FLOAT:
               return operator+(b, a);

            default:
               break;
         }

         return variant_data_t(a.int_value() + b.to_int(), a._type);
      }

      case type_t::FLOAT:
      {
         switch (b._type)
         {
            case type_t::DOUBLE:
               return operator+(b, a);

            default:
               break;
         }

         return variant_data_t(a.float_value() + b.to_float(), a._type);
      }

      case type_t::DOUBLE:
      {
         return variant_data_t(a.double_value() + b.to_double(), a._type);
      }

      case type_t::STRING:
      {
         return variant_data_t(a.string_value() + b.to_string(), a._type);
      }

      case type_t::BYTE:
      {
         switch (b._type)
         {
            case type_t::INTEGER:
            case type_t::FLOAT:
            case type_t::DOUBLE:
               return operator+(b, a);

            default:
               break;
         }

         return variant_data_t(a.byte_value() + b.to_byte(), a._type);
      }

      case type_t::BOOLEAN:
      {
         return variant_data_t(a.bool_value() || b.to_bool(), a._type);
      }

      default:
         break;
   }

   return variant_data_t();
}


/* -------------------------------------------------------------------------- */

variant_data_t operator-(const variant_data_t & a, const variant_data_t & b)
{
   variant_data_t c(b);
   bool res = c.invert_sign();

   rt_error_code_t::get_instance().throw_if(
      !res,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   return operator+(a,c);
}


/* -------------------------------------------------------------------------- */

variant_data_t operator*(const variant_data_t & a, const variant_data_t & b)
{
   using type_t = variant_data_t::type_t;

   rt_error_code_t::get_instance().throw_if(
      a._type == type_t::UNDEFINED ||
      b._type == type_t::UNDEFINED,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   switch (a._type)
   {
      case type_t::INTEGER:
      {
         switch (b._type)
         {
            case type_t::DOUBLE:
            case type_t::FLOAT:
               return operator*(b, a);

            default:
               break;
         }

         return variant_data_t(a.int_value() * b.to_int(), a._type);
      }

      case type_t::FLOAT:
      {
         switch (b._type)
         {
            case type_t::DOUBLE:
               return operator*(b, a);

            default:
               break;
         }

         return variant_data_t(a.float_value() * b.to_float(), a._type);
      }

      case type_t::DOUBLE:
         return variant_data_t(a.double_value() * b.to_double(), a._type);

      case type_t::STRING:
         return variant_data_t(a.string_value() * b.to_string(), a._type);

      case type_t::BYTE:
      {
         switch (b._type)
         {
            case type_t::INTEGER:
            case type_t::FLOAT:
            case type_t::DOUBLE:
               return operator*(b, a);

            default:
               break;
         }

         return variant_data_t(a.byte_value() * b.to_byte(), a._type);
      }

      case type_t::BOOLEAN:
         return variant_data_t(a.bool_value() && b.to_bool(), a._type);

      default:
         break;
   }

   return variant_data_t();
}


/* -------------------------------------------------------------------------- */

variant_data_t operator/(const variant_data_t & a, const variant_data_t & b)
{
   using type_t = variant_data_t::type_t;

   rt_error_code_t::get_instance().throw_if(
      a._type == type_t::UNDEFINED ||
      b._type == type_t::UNDEFINED,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   switch (a._type)
   {
      case type_t::INTEGER:
      {
         switch (b._type)
         {
            case type_t::DOUBLE:
            case type_t::FLOAT:
            {
               auto c=b;

               rt_error_code_t::get_instance().throw_if(
                  c.to_double() == 0,
                  0,
                  rt_error_code_t::E_DIV_BY_ZERO,
                  "");

               bool res = c.invert();

               rt_error_code_t::get_instance().throw_if(
                  !res,
                  0,
                  rt_error_code_t::E_TYPE_ILLEGAL,
                  "");

               return operator*(a, c);
            }

            default:
               break;
         }

         rt_error_code_t::get_instance().throw_if(
            b.to_int() == 0,
            0,
            rt_error_code_t::E_DIV_BY_ZERO,
            "");

         return variant_data_t(a.int_value() / b.to_int(), a._type);
      }

      case type_t::FLOAT:
      {
         switch (b._type)
         {
            case type_t::DOUBLE:
            {
               auto c = b;

               rt_error_code_t::get_instance().throw_if(
                  c.to_double() == 0,
                  0,
                  rt_error_code_t::E_DIV_BY_ZERO,
                  "");

               bool res = c.invert();

               rt_error_code_t::get_instance().throw_if(
                  !res,
                  0,
                  rt_error_code_t::E_TYPE_ILLEGAL,
                  "");

               return operator*(a, c);
            }

            default:
               break;
         }

         auto c = b.to_float();

         rt_error_code_t::get_instance().throw_if(
            c == 0,
            0,
            rt_error_code_t::E_DIV_BY_ZERO,
            "");

         return variant_data_t(a.float_value() / c, a._type);
      }

      case type_t::DOUBLE:
      {
         auto c = b.to_double();

         rt_error_code_t::get_instance().throw_if(
            c == 0,
            0,
            rt_error_code_t::E_DIV_BY_ZERO,
            "");

         return variant_data_t(a.double_value() / c, a._type);
      }


      case type_t::BYTE:
      {
         auto c = b.to_byte();

         rt_error_code_t::get_instance().throw_if(
            c == 0,
            0,
            rt_error_code_t::E_DIV_BY_ZERO,
            "");

         return variant_data_t(a.byte_value() / c, a._type);
      }

      case type_t::BOOLEAN:
      case type_t::STRING:
         rt_error_code_t::get_instance().throw_if(
            true,
            0,
            rt_error_code_t::E_TYPE_MISMATCH,
            "");

         break;
   }

   return variant_data_t();
}


/* -------------------------------------------------------------------------- */

void variant_data_t::cast_set(const variant_data_t & other)
{
   if (this != &other)
   {
      switch (_type)
      {
         case type_t::UNDEFINED:
            break;

         case type_t::INTEGER:
            int_value() = other.to_int();
            break;

         case type_t::FLOAT:
            float_value() = other.to_float();
            break;

         case type_t::DOUBLE:
            double_value() = other.to_double();
            break;

         case type_t::STRING:
            string_value() = other.to_string();
            break;

         case type_t::BYTE:
            byte_value() = other.to_byte();
            break;

         case type_t::BOOLEAN:
            bool_value() = other.to_bool();
            break;

         default:
            break;
      }
   }
}

/* -------------------------------------------------------------------------- */

variant_data_t& variant_data_t::operator=(const variant_data_t & other)
{
   set(other);
   return *this;
}


/* -------------------------------------------------------------------------- */

void variant_data_t::check_type(variable_t::type_t t) const
{
   rt_error_code_t::get_instance().throw_if(
      _ptr == nullptr,
      0,
      rt_error_code_t::E_TYPE_ILLEGAL,
      "");

   rt_error_code_t::get_instance().throw_if(
      _type != t,
      0,
      rt_error_code_t::E_TYPE_MISMATCH,
      "");
}


/* -------------------------------------------------------------------------- */

void variant_data_t::free_mem()
{
   if (!_ptr)
      return;

   switch (_type)
   {
      case type_t::UNDEFINED:
         break;

      case type_t::INTEGER:
         delete &(int_value());
         break;

      case type_t::FLOAT:
         delete &(float_value());
         break;

      case type_t::DOUBLE:
         delete &(double_value());
         break;

      case type_t::STRING:
         delete &(string_value());
         break;

      case type_t::BYTE:
         delete &(byte_value());
         break;

      case type_t::BOOLEAN:
         delete &(bool_value());
         break;

      default:
         assert(0);
         break;
   }

   _ptr = nullptr;
}


/* -------------------------------------------------------------------------- */

string_t variant_data_t::to_string() const
{
   switch (_type)
   {
      case type_t::UNDEFINED:
         return string_t();

      case type_t::INTEGER:
         return std::to_string(int_value());

      case type_t::FLOAT:
         return std::to_string(float_value());

      case type_t::DOUBLE:
         return std::to_string(double_value());

      case type_t::STRING:
         return string_value();

      case type_t::BYTE:
         return std::to_string(byte_value());

      case type_t::BOOLEAN:
         return string_t(bool_value() ? "true" : "false");

      default:
         break;
   }

   return string_t();
}


/* -------------------------------------------------------------------------- */

int_t variant_data_t::to_int() const
{
   switch (_type)
   {
      case type_t::UNDEFINED:
         return 0;

      case type_t::INTEGER:
         return int_value();

      case type_t::FLOAT:
         return int_t(float_value());

      case type_t::DOUBLE:
         return int_t(double_value());

      case type_t::STRING:
         return int_t(std::stoi(string_value()));

      case type_t::BYTE:
         return int_t(byte_value() & 0xff);

      case type_t::BOOLEAN:
         return int_t(bool_value() ? 1 : 0);

      default:
         break;
   }

   return 0;
}


/* -------------------------------------------------------------------------- */

byte_t variant_data_t::to_byte() const
{
   switch (_type)
   {
      case type_t::UNDEFINED:
         return 0;

      case type_t::INTEGER:
         return byte_t(int_value() & 0xff);

      case type_t::FLOAT:
         return byte_t(int(float_value()) & 0xff);

      case type_t::DOUBLE:
         return byte_t(int(double_value()) & 0xff);

      case type_t::STRING:
         return byte_t(std::stoi(string_value()) & 0xff);

      case type_t::BYTE:
         return byte_value();

      case type_t::BOOLEAN:
         return byte_t(bool_value() ? 1 : 0);

      default:
         break;
   }

   return false;
}


/* -------------------------------------------------------------------------- */

bool variant_data_t::to_bool() const
{
   switch (_type)
   {
      case type_t::UNDEFINED:
         return false;

      case type_t::INTEGER:
         return int_value() ? true : false;

      case type_t::FLOAT:
         return float_value() ? true : false;

      case type_t::DOUBLE:
         return double_value() ? true : false;

      case type_t::STRING:
         return string_value() == "true" ? true : false;

      case type_t::BYTE:
         return byte_value() ? true : false;

      case type_t::BOOLEAN:
         return bool_value();

      default:
         break;
   }

   return false;
}


/* -------------------------------------------------------------------------- */

double_t variant_data_t::to_double() const
{
   switch (_type)
   {
      case type_t::UNDEFINED:
         return 0;

      case type_t::INTEGER:
         return double_t(int_value());

      case type_t::FLOAT:
         return double_t(float_value());

      case type_t::DOUBLE:
         return double_value();

      case type_t::STRING:
         return double_t(std::stod(string_value()));

      case type_t::BYTE:
         return double_t(byte_value() & 0xff);

      case type_t::BOOLEAN:
         return double_t(bool_value() ? 1 : 0);

      default:
         break;
   }

   return 0;
}


/* -------------------------------------------------------------------------- */

float_t variant_data_t::to_float() const
{
   switch (_type)
   {
      case type_t::UNDEFINED:
         return 0;

      case type_t::INTEGER:
         return float_t(int_value());

      case type_t::FLOAT:
         return float_value();

      case type_t::DOUBLE:
         return float_t(double_value());

      case type_t::STRING:
         return float_t(std::stod(string_value()));

      case type_t::BYTE:
         return float_t(byte_value() & 0xff);

      case type_t::BOOLEAN:
         return float_t(bool_value() ? 1 : 0);

      default:
         break;
   }

   return 0;
}


/* -------------------------------------------------------------------------- */

}
